import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;




public class Client{
	private static int clientCounter = 1;
	//General client data
	private int clientID;
	private String name = "Unknown";
	private String webID = "";
	private boolean isReadyInLobby = false;
	private boolean hasLoadedLevel = false;
	
	private boolean connected = true;
	private boolean useTCPTimer = false;
	private Socket socket;
	private TCPReceiver receiver;
	private TCPSender sender;
	private PlayerData playerData;
	
	private int clientUdpPort;
	
	private int pingCounter = 0;
	private LinkedList<Integer> pingBuffer = new LinkedList<Integer>();
	private HashMap<Integer, Long> pingStartTimes = new HashMap<Integer, Long>();
	
	public Client(Socket socket){
		this.clientID = clientCounter;
		clientCounter ++;
		//System.out.println("Client #" + clientID + " connected: " + socket.getInetAddress() + " on port " + socket.getPort());
		this.socket = socket;
		playerData = new PlayerData(this);
		receiver = new TCPReceiver(this, socket);
		sender = new TCPSender(this, socket);
		receiver.start();
	}
	
	/**
	 * Sends data through TCPConnection.
	 * @param data The data to be sent. Do not end with a newline.
	 */
	public void sendToClient(String data){
		sender.addToBuffer(data);
	}
	
	public void disconnect(){
		System.out.println("< Connection lost with " + this.name);
		this.receiver = null;
		this.sender.disconnect();
		this.sender = null;
		this.connected = false;
		ConnectionHandler.getInstance().clientDisconnected(this);
		UserInterface.updateClients();
		
	}
	
	public void reconnectClient(Socket newSocket){
		this.connected = true;
		this.socket = newSocket;
		receiver = new TCPReceiver(this, this.socket);
		sender = new TCPSender(this, this.socket);
		receiver.start();
		UserInterface.updateClients();
		System.out.println("> " + this.name + " reconnected."); //nieuwe receiver aanmaken?
	}
	
	public void setReadyInLobbyStatus(boolean isReadyInLobby){
		this.isReadyInLobby = isReadyInLobby;
	}
	
	public void setHasLoadedLevel(boolean loaded){
		this.hasLoadedLevel = loaded;
	}
	
	public void setName(String name){
		this.name = name;
	}
	
	public void setWebID(String webid){
		this.webID = webid;
	}
	
	public String getName(){
		return this.name;
	}
	
	public String getWebId(){
		return this.webID;
	}
	
	public boolean isConnected(){
		return this.connected;
	}
	
	public boolean isReadyInLobby(){
		return this.isReadyInLobby;
	}
	
	public boolean hasLoadedLevel(){
		return this.hasLoadedLevel;
	}
	
	public InetAddress getClientAddress(){
		return this.socket.getInetAddress();
	}
	
//	public int getClientPort(){
//		return this.socket.getPort();
//	}
	
	public int getClientUdpPort(){
		return this.clientUdpPort;
	}
	
	public void setClientUdpPort(int port){
		this.clientUdpPort = port;
	}
	
	public int getClientId(){
		return this.clientID;
	}
	
	/**
	 * Gets the current ping of the client, taking the latest ping in the buffer.
	 * @return The current ping of the client (0 if the pingbuffer is empty).
	 */
	public int getPing()
	{
		int ping = 0;
		int bufferCount = this.pingBuffer.size();
		if (bufferCount > 0)
		{
			ping = this.pingBuffer.get(bufferCount - 1);
		}
		return ping;
	}
	
	/**
	 * Gets the ping of this client
	 * @return
	 */
	public int getAveragePing(){
		int ping = 0;
		int bufferCount = this.pingBuffer.size();
		if (bufferCount > 0)
		{
			int averagePing = 0;
			for (int i = 0; i < bufferCount; i++)
			{
				// Add all pings in the buffer
				averagePing += this.pingBuffer.get(i);
			}
			// Calculate the average ping
			averagePing = averagePing / bufferCount;
			ping = averagePing;
		}
		return ping;
	}
	
	public PlayerData getPlayerData(){
		return this.playerData;
	}
	
	/**
	 * Adds a ping value to the client's ping buffer.
	 * @param ping The ping value to add to the buffer.
	 */
	public void addToPingBuffer(int ping)
	{
		this.pingBuffer.add(ping);
		if (this.pingBuffer.size() > Settings.pingBufferSize)
		{
			this.pingBuffer.remove();
		}
	}
	
	/**
	 * Gets the ping counter, which is incremented every time a ping request is sent to this client.
	 * @return The ping counter.
	 */
	public int getPingCounter() {
		return pingCounter;
	}
	
	public void setPingCounter(int pingCounter) {
		this.pingCounter = pingCounter;
	}

	/**
	 * Gets the hashmap containing the id's of the ping that have been 
	 * sent to the client, and the corresponding timestamps.
	 * @return The ping id's and corresponding timestamps.
	 */
	public HashMap<Integer, Long> getPingStartTimes() {
		return pingStartTimes;
	}
	
	@Override
	public String toString(){
		return this.name;
	}
	
}
